Security News
JSR Working Group Kicks Off with Ambitious Roadmap and Plans for Open Governance
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
@loopback/metadata
Advanced tools
Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata
This module contains utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata.
import {ClassDecoratorFactory} from '@loopback/metadata';
export interface MyClassMetadata {
name: string;
description?: string;
}
function myClassDecorator(spec: MyClassMetadata): ClassDecorator {
return ClassDecoratorFactory.createDecorator<MyClassMetadata>(
'metadata-key-for-my-class-decorator',
spec,
{decoratorName: '@myClassDecorator'},
);
}
Alternatively, we can instantiate the factory and create a decorator:
function myClassDecorator(spec: MyClassMetadata): ClassDecorator {
const factory = new ClassDecoratorFactory<MyClassMetadata>(
'metadata-key-for-my-class-decorator',
spec,
);
return factory.create();
}
Now we can use @myClassDecorator
to add metadata to a class as follows:
@myClassDecorator({name: 'my-controller'})
class MyController {}
import {MethodDecoratorFactory} from '@loopback/metadata';
export interface MyMethodMetadata {
name: string;
description?: string;
}
function myMethodDecorator(spec: MyMethodMetadata): MethodDecorator {
return MethodDecoratorFactory.createDecorator<MyMethodMetadata>(
'metadata-key-for-my-method-decorator',
spec,
);
}
Now we can use @myMethodDecorator
to add metadata to a method as follows:
class MyController {
@myMethodDecorator({name: 'my-method'})
myMethod(x: string): string {
return 'Hello, ' + x;
}
@myMethodDecorator({name: 'another-method'})
anotherMethod() {}
@myMethodDecorator({name: 'my-static-method'})
static myStaticMethod() {}
}
Instead of a single immutable object to be merged, the
MethodMultiDecoratorFactory
reduced parameters into a flat array of items.
When fetching the metadata later, you will receive it as an array.
import {MethodMultiDecoratorFactory} from '@loopback/metadata';
function myMultiMethodDecorator(spec: object): MethodDecorator {
return MethodMultiDecoratorFactory.createDecorator<object>(
'metadata-key-for-my-method-multi-decorator',
spec,
);
}
Now, you can use it multiple times on a method:
class MyController {
@myMultiMethodDecorator({x: 1})
@myMultiMethodDecorator({y: 2})
@myMultiMethodDecorator({z: 3})
public point() {}
}
class MyOtherController {
@myMultiMethodDecorator([{x: 1}, {y: 2}, {z: 3}])
public point() {}
}
And when you access this data:
const arrayOfSpecs = MetadataInspector.getMethodMetadata<object>(
'metadata-key-for-my-method-multi-decorator',
constructor.prototype,
op,
);
// [{z: 3}, {y: 2}, {x: 1}]
Typescript
applies decorators in reverse order
per class, from the parent down. The metadata array resurned by getOwnMetadata
will be in this order:
class Parent {
@myMultiMethodDecorator('A') // second
@myMultiMethodDecorator('B') // first
public greet() {}
}
class Child extends Parent {
@myMultiMethodDecorator(['C', 'D']) // [third, fourth]
public greet() {}
}
class Grandchild extends Child {
@myMultiMethodDecorator('E') // sixth
@myMultiMethodDecorator('F') // fifth
public greet() {}
}
// getMethodMetadata = ['B', 'A', 'C', 'D', 'F', 'E']
You can also create a decorator that takes an object that can contain an array:
interface Point {
x?: number;
y?: number;
z?: number;
}
interface GeometryMetadata {
points: Point[];
}
function geometry(...points: Point[]): MethodDecorator {
return MethodMultiDecoratorFactory.createDecorator<GeometryMetadata>(
'metadata-key-for-my-method-multi-decorator',
points,
);
}
class MyGeoController {
@geometry({x: 1})
@geometry({x: 2}, {y: 3})
@geometry({z: 5})
public abstract() {}
}
const arrayOfSpecs = MetadataInspector.getMethodMetadata<GeometryMetadata>(
'metadata-key-for-my-method-multi-decorator',
constructor.prototype,
op,
);
// [
// { points: [{x: 1}]},
// { points: [{x:2}, {y:3}]},
// { points: [{z: 5}]},
// ]
import {PropertyDecoratorFactory} from '@loopback/metadata';
export interface MyPropertyMetadata {
name: string;
description?: string;
}
function myPropertyDecorator(spec: MyPropertyMetadata): PropertyDecorator {
return PropertyDecoratorFactory.createDecorator<MyPropertyMetadata>(
'metadata-key-for-my-property-decorator',
spec,
);
}
Now we can use @myPropertyDecorator
to add metadata to a property as follows:
class MyController {
@myPropertyDecorator({name: 'my-property'})
myProperty: string;
@myPropertyDecorator({name: 'another-property'})
anotherProperty: boolean;
@myPropertyDecorator({name: 'my-static-property'})
static myStaticProperty: string;
}
import {ParameterDecoratorFactory} from '@loopback/metadata';
export interface MyParameterMetadata {
name: string;
description?: string;
}
function myParameterDecorator(spec: MyParameterMetadata): ParameterDecorator {
return ParameterDecoratorFactory.createDecorator<MyParameterMetadata>(
'metadata-key-for-my-parameter-decorator',
spec,
);
}
Now we can use @myParameterDecorator
to add metadata to a parameter as
follows:
class MyController {
constructor(
@myParameterDecorator({name: 'logging-prefix'}) public prefix: string,
@myParameterDecorator({name: 'logging-level'}) public level: number,
) {}
myMethod(
@myParameterDecorator({name: 'x'}) x: number,
@myParameterDecorator({name: 'y'}) y: number,
) {}
static myStaticMethod(
@myParameterDecorator({name: 'a'}) a: string,
@myParameterDecorator({name: 'b'}) b: string,
) {}
}
import {MethodParameterDecoratorFactory} from '@loopback/metadata';
export interface MyParameterMetadata {
name: string;
description?: string;
}
function myMethodParameterDecorator(
spec: MyParameterMetadata,
): MethodDecorator {
return MethodParameterDecoratorFactory.createDecorator<MyParameterMetadata>(
'metadata-key-for-my-method-parameter-decorator',
spec,
);
}
Now we can use @myMethodParameterDecorator
to add metadata to a parameter as
follows:
class MyController {
@myMethodParameterDecorator({name: 'x'})
@myMethodParameterDecorator({name: 'y'})
myMethod(x: number, y: number) {}
}
WARNING: Using method decorators to provide metadata for parameters is strongly discouraged for a few reasons:
We recommend that ParameterDecorator
be used instead.
An object of type DecoratorOptions
can be passed in to create decorator
functions. There are two flags for the options:
true
.spec
argument will be cloned.
Sometimes we use shared spec for the decoration, but the decorator function
might need to mutate the object. Cloning the input spec makes it safe to use
the same spec (template
) to decorate different members. Default to true
.@inject
for error and
debugging messages.By default, the decorator factories allow inheritance with the following rules:
If the metadata is an object, we merge the spec
argument from the
decorator function into the inherited value from base classes. For metadata
of array and other primitive types, the spec
argument is used if provided.
inherit
method of the decorator factory to customize how
to resolve spec
against the inherited metadata. For example:protected inherit(inheritedMetadata: T | undefined | null): T {
// Ignore the inherited metadata
return this.spec;
}
Method/property/parameter level metadata is applied to the class or its prototype as a map keyed method/property names. We think this approach is better than keeping metadata at method/property level as it's not easy to inspect a class to find static/instance methods and properties with decorations. The metadata for a class is illustrated below:
{
// Class level metadata
'my-class-decorator-key': MyClassMetadata,
// Static method (including the constructor) parameter metadata
'my-static-parameter-decorator-key': {
'': [MyConstructorParameterMetadata], // Constructor parameter metadata
'myStaticMethod1': [MyStaticMethodParameterMetadata],
'myStaticMethod2': [MyStaticMethodParameterMetadata],
},
// Static method metadata
'my-static-method-decorator-key': {
'myStaticMethod1': MyStaticMethodMetadata,
'myStaticMethod2': MyStaticMethodMetadata,
},
// Static property metadata
'my-static-property-decorator-key': {
'myStaticMethod1': MyStaticPropertyMetadata,
'myStaticMethod1': MyStaticPropertyMetadata,
}
}
{
// Instance method parameter metadata
'my-instance-parameter-decorator-key': {
'myMethod1': [MyMethodParameterMetadata],
'myMethod2': [MyMethodParameterMetadata],
},
// Instance method metadata
'my-instance-method-decorator-key': {
'myMethod1': MyMethodMetadata,
'myMethod2': MyMethodMetadata,
},
// Instance property metadata
'my-instance-property-decorator-key': {
'myProperty1': MyPropertyMetadata,
'myProperty2': MyPropertyMetadata,
}
}
The following methods in DecoratorFactory
allow subclasses to customize how to
merge the spec
with existing metadata for a class, methods, properties, and
method parameters. Please note M
is a map for methods/properties/parameters.
protected mergeWithInherited(
inheritedMetadata: M,
target: Object,
member?: string,
descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
): M {
// ...
}
protected mergeWithOwn(
ownMetadata: M,
target: Object,
member?: string,
descriptorOrIndex?: TypedPropertyDescriptor<any> | number,
): M {
// ...
}
@myClassDecorator({name: 'my-controller'})
@myClassDecorator({name: 'your-controller'})
class MyController {}
MetadataInspector
provides API to inspect metadata from a class and its
members.
import {MetadataInspector} from '@loopback/metadata';
const meta = MetadataInspector.getClassMetadata(
'my-class-decorator-key',
MyController,
);
import {MetadataInspector} from '@loopback/metadata';
const meta = MetadataInspector.getClassMetadata<MyClassMetaData>(
'my-class-decorator-key',
MyController,
{
ownMetadataOnly: true,
},
);
import {MetadataInspector} from '@loopback/metadata';
const allMethods = MetadataInspector.getAllMethodMetaData<MyMethodMetadata>(
'my-method-decorator-key',
MyController.prototype, // Use MyController for static methods
);
const myMethod = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
'my-method-decorator-key',
MyController.prototype, // Use MyController for static methods
'myMethod',
);
import {MetadataInspector} from '@loopback/metadata';
const allProps = MetadataInspector.getAllPropertyMetaData<MyPropertyMetadata>(
'my-property-decorator-key',
MyController.prototype, // Use MyController for static properties
);
const myProp = MetadataInspector.getMethodMetaData<MyMethodMetadata>(
'my-property-decorator-key',
MyController.prototype, // Use MyController for static properties
'myProp',
);
import {MetadataInspector} from '@loopback/metadata';
const allParamsForMyMethod =
MetadataInspector.getAllParameterMetaData<MyParameterMetadata>(
'my-parameter-decorator-key',
MyController.prototype, // Use MyController for static methods,
'myMethod',
);
const firstParamForMyMethod =
MetadataInspector.getMyParameterMetaData<MyParameterMetadata>(
'my-parameter-decorator-key',
MyController.prototype, // Use MyController for static methods
'myMethod',
0, // parameter index
);
const allParamsForConstructor =
MetadataInspector.getAllParameterMetaData<MyParameterMetadata>(
'my-parameter-decorator-key',
MyController,
'',
);
You can use MetadataAccessor to provide type checks for metadata access via keys. For example,
const CLASS_KEY = MetadataAccessor.create<MyClassMetadata, ClassDecorator>(
'my-class-decorator-key',
);
// Create a class decorator with the key
const myClassDecorator = ClassDecoratorFactory.createDecorator(CLASS_KEY);
// Inspect a class with the key
const myClassMeta = MetadataInspector.getClassMetaData(CLASS_KEY, MyController);
Please note MetadataKey can be an instance of MetadataAccessor or a string.
import {MetadataInspector} from '@loopback/metadata';
const myPropType = MetadataInspector.getDesignTypeForProperty(
MyController.prototype,
'myProp',
);
const myConstructor = MetadataInspector.getDesignTypeForMethod(
MyController,
'',
);
const myMethod = MetadataInspector.getDesignTypeForMethod(
MyController.prototype, // Use MyController for static methods
'myMethod',
);
npm install --save @loopback/metadata
Run npm test
from the root folder.
See all contributors.
MIT
FAQs
Utilities to help developers implement TypeScript decorators, define/merge metadata, and inspect metadata
The npm package @loopback/metadata receives a total of 18,099 weekly downloads. As such, @loopback/metadata popularity was classified as popular.
We found that @loopback/metadata demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 7 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
At its inaugural meeting, the JSR Working Group outlined plans for an open governance model and a roadmap to enhance JavaScript package management.
Security News
Research
An advanced npm supply chain attack is leveraging Ethereum smart contracts for decentralized, persistent malware control, evading traditional defenses.
Security News
Research
Attackers are impersonating Sindre Sorhus on npm with a fake 'chalk-node' package containing a malicious backdoor to compromise developers' projects.